/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */
package org.opensourcephysics.display;
import java.awt.*;
import java.awt.geom.GeneralPath;

/**
 * Draws a coil spring in a drawing panel.
 * @author F. Esquembre
 * @author W. Christian
 * @version 1.0
 */
public class Spring implements Measurable {

   private GeneralPath springPath = new GeneralPath();
   // Configuration variables
   protected boolean thinExtremes = true;
   protected boolean visible = true;
   protected int loops = -1, pointsPerLoop = -1;            // -1 to make sure the arrays are allocated
   protected float x = 0.0f, y = 0.0f;
   protected float sizex = 0.1f, sizey = 0.0f;

   /**
    * The radius of the spring (normal to its direction)
    */
   protected float radius = 0.1f;
   protected float solenoid = 0.0f;
   protected Color edgeColor = Color.BLACK;
   protected Stroke edgeStroke = new BasicStroke(1.0f);
   // Implementation variables
   protected boolean hasChanged = true, zeroLength = false; // Whether the element should recompute data that depends on position, size, scale, resolution, ...
   private int segments = 0;
   private float xPoints[] = null, yPoints[] = null;

   /**
    * Constructs a 0.1 radius Spring.
    */
   public Spring() {
      this(0.1);
   }

   /**
    * Special constructor that allows to specify the radius of the spring
    * @param _radius the radius of the spring (normal to its direction)
    */
   public Spring(double _radius) {
      setRadius(_radius);
      setResolution(8, 15);
   }

   // -------------------------------------
   // Configuration methods
   // -------------------------------------

   /**
    * Sets the X position of the origin of the spring
    * @param x double
    */
   public void setX(double x) {
      this.x = (float) x;
      hasChanged = true;
   }

   /**
    * Gets the X position of the origin of the spring
    * @return double
    */
   public double getX() {
      return this.x;
   }

   /**
    * Sets the Y position of the origin of the spring
    * @param y double
    */
   public void setY(double y) {
      this.y = (float) y;
      hasChanged = true;
   }

   /**
    * Gets the Y position of the origin of the spring
    * @return double
    */
   public double getY() {
      return this.y;
   }

   /**
    * Sets the position of the origin of the spring
    * @param x double
    * @param y double
    */
   public void setXY(double x, double y) {
      this.x = (float) x;
      this.y = (float) y;
      hasChanged = true;
   }

   /**
    * Sets the X size of the spring
    * @param sizeX double
    */
   public void setSizeX(double sizeX) {
      this.sizex = (float) sizeX;
      hasChanged = true;
   }

   /**
    * Gets the X size of the spring
    * @return double
    */
   public double getSizeX() {
      return this.sizex;
   }

   /**
    * Sets the Y size of the spring
    * @param sizeY double
    */
   public void setSizeY(double sizeY) {
      this.sizey = (float) sizeY;
      hasChanged = true;
   }

   /**
    * Gets the Y size of the spring
    * @return double
    */
   public double getSizeY() {
      return this.sizey;
   }

   /**
    * Sets the size of the spring
    * @param sizeX double
    * @param sizeY double
    */
   public void setSizeXY(double sizeX, double sizeY) {
      this.sizex = (float) sizeX;
      this.sizey = (float) sizeY;
      hasChanged = true;
   }

   /**
    * Set the radius of the spring.
    * @param _radius the radius of the spring (normal to its direction)
    */
   public void setRadius(double radius) {
      this.radius = (float) radius;
      hasChanged = true;
   }

   /**
    * Get the radius of the spring.
    */
   public double getRadius() {
      return this.radius;
   }

   /**
    * Sets the visibiliby of the spring
    * @param _visible boolean
    */
   public void setVisible(boolean visible) {
      this.visible = visible;
   }

   public boolean isVisible() {
      return this.visible;
   }

   public void setEdgeColor(Color color) {
      this.edgeColor = color;
   }

   public Color getEdgeColor() {
      return this.edgeColor;
   }

   public void setEdgeStroke(Stroke stroke) {
      this.edgeStroke = stroke;
   }

   public Stroke getEdgeStroke() {
      return this.edgeStroke;
   }

   /**
    * Sets the number of spires and points per spire used to draw the spring
    * @param nLoops int
    * @param nPointsPerLoop int
    */
   public void setResolution(int nLoops, int nPointsPerLoop) {
      if((nLoops==loops)&&(nPointsPerLoop==pointsPerLoop)) {
         return; // No need to reallocate arrays
      }
      this.loops = nLoops;
      this.pointsPerLoop = nPointsPerLoop;
      segments = loops*pointsPerLoop;
      int n = segments+1;
      xPoints = new float[n];
      yPoints = new float[n];
      hasChanged = true;
   }

   public int getLoops() {
      return this.loops;
   }

   public int getPointsPerLoop() {
      return this.pointsPerLoop;
   }

   /**
    * Sets a double factor that makes the spring look like a solenoid by
    * causing the spires to go back and forth
    * Default is 0, which makes a standard spring
    * @param factor double
    */
   public void setSolenoid(double factor) {
      solenoid = (float) factor;
      hasChanged = true;
   }

   /**
    * Whether the spring should show thin extremes. Default is true
    * @param thin boolean
    */
   public void setThinExtremes(boolean thin) {
      thinExtremes = thin;
      hasChanged = true;
   }

   /**
    * Implementation of Drawable.
    * @param panel DrawingPanel
    * @param g Graphics
    */
   public void draw(DrawingPanel panel, Graphics g) {
      if(!visible) {
         return;
      }
      if(hasChanged) {
         computePoints();
      }
      Graphics2D g2 = (Graphics2D) g;
      g2.setStroke(edgeStroke);
      g2.setColor(edgeColor);
      if(zeroLength) {
         int a = panel.xToPix(x), b = panel.yToPix(y);
         g2.drawLine(a, b, a, b);
         return;
      }
      Shape s = springPath.createTransformedShape(panel.getPixelTransform());
      g2.draw(s);
   }

   // -------------------------------------
   // Implementation of Measured3D
   // -------------------------------------
   public boolean isMeasured() {
      return visible;
   }

   public double getXMin() {
      return (sizex>0) ? x : x+sizex;
   }

   public double getXMax() {
      return (sizex>0) ? x+sizex : x;
   }

   public double getYMin() {
      return (sizey>0) ? y : y+sizey;
   }

   public double getYMax() {
      return (sizey>0) ? y+sizey : y;
   }

   // -------------------------------------
   //  Private methods
   // -------------------------------------
   private void computeGeneralPath() {
      if((xPoints==null)||(xPoints.length<2)) {
         return;
      }
      int n = xPoints.length;
      springPath = new GeneralPath();
      springPath.moveTo(xPoints[0], yPoints[0]);
      for(int i = 1; i<n; i++) {
         springPath.lineTo(xPoints[i], yPoints[i]);
      }
   }

   private void computePoints() {
      float length = sizex*sizex+sizey*sizey;
      if(length==0) {
         zeroLength = true;
         return;
      }
      zeroLength = false;
      length = (float) Math.sqrt(length);
      float u2x = -sizey/length, u2y = sizex/length;
      float delta = (float) (2.0f*Math.PI/pointsPerLoop);
      if(radius<0) {
         delta *= -1;
      }
      int pre = pointsPerLoop/2;
      for(int i = 0; i<=segments; i++) {
         int k;
         if(thinExtremes) {
            if(i<pre) {
               k = 0;
            } else if(i<pointsPerLoop) {
               k = i-pre;
            } else if(i>(segments-pre)) {
               k = 0;
            } else if(i>(segments-pointsPerLoop)) {
               k = segments-i-pre;
            } else {
               k = pre;
            }
         } else {
            k = pre;
         }
         float angle = (float) (Math.PI/2+i*delta);
         float cos = (float) Math.cos(angle); //, sin = Math.sin(angle);
         xPoints[i] = (x+i*sizex/segments+k*radius*cos*u2x/pre);
         yPoints[i] = (y+i*sizey/segments+k*radius*cos*u2y/pre);
         if(solenoid!=0.0) {
            double cte = k*Math.cos(i*2*Math.PI/pointsPerLoop)/pre;
            xPoints[i] += solenoid*cte*sizex;
            yPoints[i] += solenoid*cte*sizey;
         }
      }
      computeGeneralPath();
      hasChanged = false;
   }
}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
